#pragma once

#include <zenox/glm.hpp>
#include <zenoui/Rect.hpp>

namespace zenoui {

struct Transform { // local to world transformation presentation class, currently limited to uniform scaling and 2d translation
    glm::vec2 translation = {0, 0}; // children uniform offset (px) only work with AbsoluteLayout
    float scaling = 1; // children uniform scaling based on left-top pivot (factor) only work with AbsoluteLayout

    glm::vec2 abs_to(glm::vec2 const &pos) const { // map absolute coordinates from global space to local space
        return pos * scaling + translation;
    }

    glm::vec2 rel_to(glm::vec2 const &dir) const { // map relative coordinates from global space to local space
        return dir * scaling;
    }

    glm::vec2 abs_from(glm::vec2 const &pos) const { // map absolute coordinates from local space to global space
        return (pos - translation) / scaling;
    }

    glm::vec2 rel_from(glm::vec2 const &dir) const { // map relative coordinates from local space to global space
        return dir / scaling;
    }

    Rect rect_to(Rect const &rect) const { // map a rectangle from global space to local space
        return Rect{abs_to(rect.m_topleft), rel_to(rect.m_size)};
    }

    Rect rect_from(Rect const &rect) const { // map a rectangle from local space to global space
        return Rect{abs_from(rect.m_topleft), rel_from(rect.m_size)};
    }

    Transform deepcopy() const { // deep-copy the matrix, so that following inplace chain actions not to modify a read-only place
        return Transform{*this};
    }

    Transform &identity() { // set this transformation to identity matrix (inplace), m->identity() is equivalent to m = Transform{}
        translation = {0, 0};
        scaling = 1;
        return *this;
    }

    Transform &inverse() { // set this transformation to its inverse (inplace), combine with deepcopy if you need non-inplace inverse
        scaling = 1 / scaling;
        translation = -translation * scaling;
        return *this;
    }

    Transform &post_scale(float scale) { // append a scale operation to the current transform (inplace)
        translation *= scale;
        scaling *= scale;
        return *this;
    }

    Transform &post_translate(glm::vec2 const &translate) { // append a translate operation to the current transform (inplace)
        translation += translate;
        return *this;
    }

    Transform &pre_scale(float scale) { // prepend a scale operation to the current transform (inplace)
        scaling *= scale;
        return *this;
    }

    Transform &pre_translate(glm::vec2 const &translate) { // prepend a translate operation to the current transform (inplace)
        translation += translate * scaling;
        return *this;
    }

    Transform &post_merge(Transform const &other) { // append another transform to the current transform (inplace)
        // pos = (pos * scaling + translation) * other.scaling + other.translation;
        translation *= other.scaling;
        translation += other.translation;
        scaling *= other.scaling;
        return *this;
    }

    Transform &pre_merge(Transform const &other) { // prepend another transform to the current transform (inplace)
        // pos = (pos * other.scaling + other.translation) * scaling + translation;
        translation += other.translation * scaling;
        scaling *= other.scaling;
        return *this;
    }
};

}
